package com.fuda.uifinder

import android.util.Log
import io.zenoh.Session
import io.zenoh.exceptions.ZenohException
import io.zenoh.keyexpr.KeyExpr
import io.zenoh.publication.CongestionControl
import io.zenoh.sample.Sample
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class ZenohManager() {

    companion object {
        val TAG: String = ZenohManager::class.java.simpleName
        const val ZENOH_RESPONSE_TOPIC = "trade/response"
        const val ZENOH_COMMANDS_TOPIC = "trade/commands"
        const val ZENOH_HEARTBEAT_TOPIC= "trade/heartbeat"

        val heartbeatKeyExpr:KeyExpr? = try{
            KeyExpr.tryFrom(ZENOH_HEARTBEAT_TOPIC)
        }catch (e: ZenohException) {
            Log.e(TAG, "Failed to create KeyExpr ", e)
            null
        }

        val commandsKeyExpr:KeyExpr? = try{
            KeyExpr.tryFrom(ZENOH_COMMANDS_TOPIC)
        }catch (e: ZenohException) {
            Log.e(TAG, "Failed to create KeyExpr ", e)
            null
        }

        val responseKeyExpr:KeyExpr? = try{
            KeyExpr.tryFrom(ZENOH_RESPONSE_TOPIC)
        }catch (e: ZenohException) {
            Log.e(TAG, "Failed to create KeyExpr ", e)
            null
        }

        private val zenohCoroutineScope = CoroutineScope(Dispatchers.IO)

    }

    private lateinit var session: Session
    val listeners = mutableMapOf<String, MutableList<ZenohListener>>()
    private var isSubscribed = mutableMapOf<String, Boolean>()


    init {
        try {
            session = Session.open()
        } catch (e: Exception) {
            // Handle initialization errors
            Log.e(TAG, "Failed to open Zenoh session", e)
        }
    }


    fun publish(keyExpr: KeyExpr, payload:String){
        val publisher = session.declarePublisher(
            keyExpr
        ).congestionControl(CongestionControl.BLOCK).res()

        publisher.put(payload).res()

        publisher.close()
    }

    fun subscribe(keyExpr: KeyExpr, listener: ZenohListener) {
        val keyString = keyExpr.toString()

        synchronized(listeners) {
            listeners.computeIfAbsent(keyString) { mutableListOf() }.also { listenersList ->
                if (!listenersList.contains(listener)) {
                    listenersList.add(listener)
                }
            }

            if (isSubscribed[keyString] == null) {
                // has to use CoroutineScope, or else will block ui thread
                zenohCoroutineScope.launch {
                try {
                    session.declareSubscriber(keyExpr).res().use { subscriber ->
                        val receiver = checkNotNull(subscriber.receiver)
                        while (true) {
                            val wrapper = receiver.take()
                            val sample = wrapper.get()
                            Log.d(TAG,"\n\n")
                            Log.i(TAG, "@@ listeners= $listeners for ${sample.keyExpr}")

                            Log.i(
                                TAG,
                                ">> [ZenohManager@$this] Received " + sample.kind + " ('" + sample.keyExpr + "': '" + sample.value + "')"
                            )

                            val sampleKeyString = sample.keyExpr.toString()
                            listeners[sampleKeyString]?.forEach { l ->
                                l.onSampleReceived(sample)
                            }
                        }
                    }
                } catch (e: ZenohException) {
                    Log.e(TAG, "Failed to create KeyExpr for subscription", e)
                }
            }
                isSubscribed[keyString] = true
            }
        }
    }

    fun unsubscribe(keyExpr: KeyExpr, listener: ZenohListener) {
        val keyString = keyExpr.toString()
        synchronized(listeners) {
            listeners[keyString]?.remove(listener)
        }
    }


    fun interface ZenohListener {
        fun onSampleReceived(sample: Sample)
    }

}